I’m looking at games sales data - This is hopefully going to look like a combination of total sales stats, trends by genre (if there is that much detail), reviews(?)

Haven’t fully decided on the question I want to tackle, but I think I’m approaching the scenario in a similar way to the brief

“Small games company want to understand what types of games sell a lot of copies. In particular they are looking for analysis that helps them decide which direction to take their company in.”

I’m not very happy with the dataset, as the landscape in 2019/16 was vastly different to 2023.

Issues i’m expecting

Assumptions

Before I even look at the data, I’m expecting some bias - Newer consoles might not be represented in data, which might “suggest” that it’s better to develop a title for an older system, as they have “more success”.

Some titles will blow others out the water in terms of sales, simply due to them being multi-platform. For example, a successful PS4 exclusive title will likely not sell as many copies as a successful multi-platform title

One of the biggest influences on sales will be Title/Franchise - If a game is called “Pokemon”, chances are it’ll do well. This is not something that a small company will be able to emulate, unless they are looking to get sued.

VAMPIRE SURVIVORS DEFIES ALL LOGIC AND PREDICTIONS - PERHAPS MANKIND WASNT MEANT TO KNOW WHAT GAMES WILL TAKE OFF

Currently i have a few datasets that might be useful for supplementing the 2019 data from one of the example briefs

Hypothesis test - Franchise v non-Franchise

library(tidyverse)
library(tidytext)
library(ggwordcloud)
library(janitor)

steam <- read_csv("raw_data/steam.csv") # absolutely massive
Rows: 27075 Columns: 18── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (8): name, developer, publisher, platforms, categories, genres, steamspy_tags, owners
dbl  (9): appid, english, required_age, achievements, positive_ratings, negative_ratings, average_playtime, median_playtime, price
date (1): release_date
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
games_1 <- read_csv("raw_data/Video_Games.csv")
Rows: 16719 Columns: 16── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (8): Name, Platform, Year_of_Release, Genre, Publisher, User_Score, Developer, Rating
dbl (8): NA_Sales, EU_Sales, JP_Sales, Other_Sales, Global_Sales, Critic_Score, Critic_Count, User_Count
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
games_2 <- read_csv("raw_data/Video_Games_Sales.csv")
Rows: 16719 Columns: 16── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (8): Name, Platform, Year_of_Release, Genre, Publisher, User_Score, Developer, Rating
dbl (8): NA_Sales, EU_Sales, JP_Sales, Other_Sales, Global_Sales, Critic_Score, Critic_Count, User_Count
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
games_raw <- read_csv("raw_data/Raw Data GVGS&R.csv")
Rows: 16719 Columns: 16── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (8): Name, Platform, Year_of_Release, Genre, Publisher, User_Score, Developer, Rating
dbl (8): NA_Sales, EU_Sales, JP_Sales, Other_Sales, Global_Sales, Critic_Score, Critic_Count, User_Count
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
games_cleaned <- read_csv("raw_data/Cleaned Data 2 GVGS&R.csv")
Rows: 6894 Columns: 15── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (5): Name, Genre, Publisher, Developer, Rating
dbl (10): Year_of_Release, NA_Sales, EU_Sales, JP_Sales, Other_Sales, Global_Sales, Critic_Score, Critic_Count, User_Score, User_Count
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
games_raw %>% 
  arrange(Name) %>% 
  drop_na(Global_Sales) # No NA's in global sales - good start
steam_tags <- steam %>% 
  select(steamspy_tags) %>% 
  separate(col = steamspy_tags, sep = ";", into = c("tags", "tags_2", "tags_3")) %>% 
  pivot_longer(col = starts_with("tags"), values_to = "all_tags") %>% 
  select(all_tags) %>% 
  drop_na() %>% 
  count(all_tags)
Warning: Expected 3 pieces. Missing pieces filled with `NA` in 2665 rows [83, 95, 96, 98, 102, 103, 106, 119, 123, 124, 125, 161, 211, 212, 213, 224, 249, 250, 261, 262, ...].
ggwordcloud(words = steam_tags$all_tags, freq = steam_tags$n, random.color = TRUE, colors =  c("#ff595e", "#ffca3a", "#8ac926", "#1982c4", "#6a4c93"))

steam %>% 
  arrange(desc(name))
games_backloggd <- read_csv("raw_data/games backloggd.csv")
New names:Rows: 1512 Columns: 14── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (12): Title, Release Date, Team, Times Listed, Number of Reviews, Genres, Summary, Reviews, Plays, Playing, Backlogs, Wishlist
dbl  (2): ...1, Rating
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Cleaning Backloggd data

games_backloggd <- games_backloggd %>% 
  clean_names()

# dropping unreleased games for now - only 3 of them, so won't impact anything too much
# converting release_date into a date and adding a years since release column - This could be usable to discount certain titles from a model
# e.g if there WAS a trend in games 10~ years ago, it may not be applicable today, and might produce incorrect guidance
# splitting genres into separate columns : only one game has 7 genre tags. 
games_backloggd <- games_backloggd %>% 
  arrange(desc(rating)) %>% 
  filter(release_date != "releases on TBD") %>% 
  mutate(release_date = mdy(release_date),
         time_since_release = as.period(today() - release_date),
         years_since_release = as.numeric(time_since_release, "years"), .after = release_date) %>% 
  mutate(years_since_release = round(years_since_release, digits = 2)) %>% 
  select(!time_since_release) %>% 
  separate(col = genres, sep = "', '", into = c("genre_tag", "genre_tag_2", "genre_tag_3", "genre_tag_4", "genre_tag_5", "genre_tag_6", "genre_tag_7"))
Warning: Expected 7 pieces. Missing pieces filled with `NA` in 1508 rows [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...].
# removing the opening and closing brackets and apostrophes across the genre columns
games_backloggd <- games_backloggd %>% 
  mutate(genre_tag = str_replace_all(genre_tag, pattern = "\\['", replacement = ""),
         across(starts_with("genre_tag"), ~ str_replace_all(., pattern = "'\\]", replacement = ""))) 

games_backloggd
NA
# Converted the below code into a function so i can reuse it
character_k_to_numeric <- function(x) {
  x = enquo(x)
  
  games_backloggd %>% 
     mutate(multiplier = case_when(
    str_detect(!!x, pattern = "K$") ~ TRUE,
    TRUE ~ FALSE)) %>% 
  mutate(!!x := str_remove(string = !!x, pattern = "K$")) %>% 
  mutate(!!x := as.numeric(!!x)) %>% 
  mutate(!!x := case_when(
    multiplier == TRUE ~ !!x*1000,
    TRUE ~ !!x
  )) %>% 
  select(!multiplier)
}

# converts these columns into numerics

games_backloggd <- character_k_to_numeric(x = number_of_reviews)

games_backloggd <- character_k_to_numeric(x = plays)

games_backloggd <- character_k_to_numeric(x = wishlist)
games_backloggd <- games_backloggd %>% 
  select(!plays, !backlogs, !times_listed)

chunk below is original attempts at converting the above columns


# games_backloggd <- games_backloggd %>% 
#   select(!times_listed)

# mutating number of reviews to be numeric
# In this horrible step, I'm using str_detect and case_when to create a column called multiplier - this will be used later
# then i drop the "K"'s, and convert the column to a numeric
# then multiply the columns values by 1000 IF the multiplier column is TRUE
# games_backloggd %>%
#   mutate(multiplier = case_when(
#     str_detect(number_of_reviews, pattern = "K$") ~ TRUE,
#     TRUE ~ FALSE
#   ),.after = number_of_reviews) %>%
#   mutate(number_of_reviews = str_remove(number_of_reviews, pattern = "K$")) %>%
#   mutate(number_of_reviews = as.numeric(number_of_reviews)) %>%
#   mutate(number_of_reviews = case_when(
#     multiplier == TRUE ~ number_of_reviews*1000,
#     TRUE ~ number_of_reviews
#   )) %>%
#   select(!multiplier)
# 
# # Oh No i need to do this to more columns
# 
# games_backloggd %>% 
#    mutate(multiplier = case_when(
#     str_detect(plays, pattern = "K$") ~ TRUE,
#     TRUE ~ FALSE
#   ),.after = plays) %>% 
#   mutate(plays = str_remove(plays, pattern = "K$")) %>% 
#   mutate(plays = as.numeric(plays)) %>% 
#   mutate(plays = case_when(
#     multiplier == TRUE ~ plays*1000,
#     TRUE ~ plays
#   )) %>% 
#   select(!multiplier)

# making it a function because that's easier
# character_k_to_numeric <- function(dataframe, column) {
#      mutate(multiplier = case_when(
#     str_detect(column, pattern = "K$") ~ TRUE,
#     TRUE ~ FALSE
#   ),.after = column) %>% 
#   mutate(column = str_remove(column, pattern = "K$")) %>% 
#   mutate(column = as.numeric(column)) %>% 
#   mutate(column = case_when(
#     multiplier == TRUE ~ column*1000,
#     TRUE ~ column
#   )) %>% 
#   select(!multiplier)
# }
# tidying up team column
games_backloggd <- games_backloggd %>% 
  mutate(team = str_replace_all(team, pattern = "\\['", replacement = ""),
         team = str_replace_all(team, pattern = "'\\]", replacement = ""),
         team = str_replace_all(team, pattern = "\\', \\'", replacement = ", ")) 
# keeping these separate incase i need them
reviews_backloggd <- games_backloggd %>% 
  select(title, summary, reviews) 
# cutting these columns for now - if needed, i'll reattach them
games_backloggd <- games_backloggd %>% 
  select(!x1)
games_backloggd <- games_backloggd %>% 
  select(-summary, -times_listed, -reviews, -playing, -backlogs) 
# removing duplicated rows

games_backloggd <- games_backloggd %>% 
  unique()

Happy with games_backlogged just now

games_backloggd_clean <- games_backloggd
write_csv(games_backloggd_clean, "clean_data/backloggd_clean.csv")

Looking at top games data

games_1 
games_2

These seem to be the same thing

going with games_1 and renaming it something more sensible

games_sales <- games_1
games_sales <- games_sales %>% 
  clean_names() 
games_sales %>% 
  filter(name == "Minecraft") %>% 
  count(sum(global_sales))
games_sales %>% 
  group_by(name) %>% 
  
Error: Incomplete expression: games_sales %>% 
  group_by(name) %>% 
  

might have to faff around to get an accurate picture of sales for this one

vg_chartz_feb23 <- read_csv("raw_data/game_statistics_feb_2023.csv")
Rows: 62326 Columns: 16── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (15): title, total_sales, total_shipped, publisher, developer, release_date, platform, japan_sales, na_sales, other_sales, pal_sales, user_score, vgchartz_score, critic_score, last_update
dbl  (1): pos
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Platform is a little unhelpful. “All” does not mean all platforms (unless Elden Ring released on Switch, or the NES) “Series” might be useful as a look-up for another table though

Thats all i want from this one i think. Might come back to it

Back to Steam

steam
# i don't know much about titles that aren't in english - are all the ones in this dataset english?

steam <- steam %>% 
  filter(english == 1) %>% 
  select(!english)

# approx 1000 aren't - but who are they?

steam %>% 
  filter(english == 0) %>% 
  filter(owners != "0-20000")
Error in `filter()`:
ℹ In argument: `english == 0`.
Caused by error:
! object 'english' not found
Backtrace:
 1. steam %>% filter(english == 0) %>% ...
 4. dplyr:::filter.data.frame(., english == 0)
 5. dplyr:::filter_rows(.data, dots, by)
 6. dplyr:::filter_eval(...)
 8. mask$eval_all_filter(dots, env_filter)
 9. dplyr (local) eval()

HYPOTHESIS TEST: IN STEAM DATA, DOES HAVING SUPPORT FOR MULTIPLE LANGUAGES AFFECT SALES?

# make platforms wider - windows_support = TRUE/FALSE, mac_support = TRUE/FALSE, linux_support = TRUE/FALSE
steam <- steam %>% 
   separate(col = platforms, sep = ";", into = c("platform_1", "platform_2", "platform_3")) 
Warning: Expected 3 pieces. Missing pieces filled with `NA` in 21957 rows [21, 25, 27, 30, 34, 35, 36, 37, 38, 39, 40, 41, 42, 45, 46, 47, 48, 49, 50, 51, ...].
# this is verbose and clunky, but it catches everything so whatever
steam <- steam %>% 
  mutate(windows_support = case_when(
    platform_1 == "windows" ~ TRUE,
    platform_2 == "windows" ~ TRUE,
    platform_3 == "windows" ~ TRUE,
    TRUE ~ FALSE ),.after = publisher) %>% 
  mutate(mac_support = case_when(
    platform_1 == "mac" ~ TRUE,
    platform_2 == "mac" ~ TRUE,
    platform_3 == "mac" ~ TRUE,
    TRUE ~ FALSE), .after = windows_support) %>% 
 mutate(linux_support = case_when(
    platform_1 == "linux" ~ TRUE,
    platform_2 == "linux" ~ TRUE,
    platform_3 == "linux" ~ TRUE,
    TRUE ~ FALSE), .after = mac_support) %>% 
  select(-platform_1, -platform_2, -platform_3)
# change owners to be more readable
steam <- steam %>% 
  mutate(owners = case_when(
    owners == "0-20000" ~ "below 20k",
    owners == "20000-50000" ~ "20k to 50k",
    owners == "50000-100000" ~ "50k to 100k",
    owners == "100000-200000" ~ "100k to 200k",
    owners == "200000-500000" ~ "200k to 500k",
    owners == "500000-1000000" ~ "500k to 1M",
    owners == "1000000-2000000" ~ "1M to 2M",
    owners == "2000000-5000000" ~ "2M to 5M",
    owners == "5000000-10000000" ~ "5M to 10M",
    owners == "10000000-20000000" ~ "10M to 20M",
    owners == "20000000-50000000" ~ "20M to 50M",
    owners == "50000000-100000000" ~ "50M to 100M",
    owners == "100000000-200000000" ~ "100M to 200M"
  )) 

  
steam %>% 
  filter(owners == "10M to 20M")

IN THIS VERSION OF THE STEAM DATASET, all but of the games listed as having more than 20 million players are freemium? free to start? free to play? (pick your poison) games

# ADDING A COLUMN TO FIND THE FREE TO PLAY STUFF

steam <- steam %>% 
  mutate(free_to_play = case_when(
    str_detect(genres, "Free to Play") ~ TRUE,
    str_detect(steamspy_tags,"Free to Play") ~ TRUE,
   # price == 0.00 ~ TRUE,
    TRUE ~ FALSE
  ),.after = publisher) 
steam %>% 
  filter(free_to_play == TRUE)
# Column to find all the VR stuff
steam <- steam %>% 
  mutate(virtual_reality_support = case_when(
    str_detect(name, "VR") ~ TRUE,
    str_detect(steamspy_tags,"VR") ~ TRUE,
    TRUE ~ FALSE
  ),.after = free_to_play)
steam %>% 
  filter(virtual_reality_support == TRUE)
# Now the same but for multi-player - Local and Online 
steam <- steam %>% 
  mutate(multiplayer = case_when(
    str_detect(categories, "Local Multi-Player") & str_detect(categories, "Online Multi-Player") & str_detect(categories, "Co-op") ~ "Local + Online + Co-op",
    str_detect(categories, "Local Multi-Player") & str_detect(categories, "Online Multi-Player") & !str_detect(categories, "Co-op") ~ "Local + Online",
    str_detect(categories, "Local Multi-Player") & str_detect(categories, "Co-op") & !str_detect(categories, "Online Multi-Player") ~ "Local + Co-op",
    str_detect(categories, "Online Multi-Player") & str_detect(categories, "Co-op") & !str_detect(categories, "Local Multi-Player") ~ "Online + Co-op",
    str_detect(categories, "Local Multi-Player") & !str_detect(categories, "Online Multi-Player") ~ "Local only",
    str_detect(categories, "Online Multi-Player") & !str_detect(categories, "Local Multi-Player") ~ "Online only",
    str_detect(categories, "Multi-player") & str_detect(categories, "MMO") | str_detect(steamspy_tags, "MMO") ~ "Online only",
    str_detect(categories, "Co-op") ~ "Co-op multiplayer",
    str_detect(categories, "Multi-player") & !str_detect(categories, "Online Multi-Player") & !str_detect(categories, "Local Multi-Player") ~ "Multiplayer (Unspecified)",
    TRUE ~ "No multiplayer"
  ),.after = virtual_reality_support) 

# I think i could add more levels, but not right now
# remember exactly one comment ago when i said "local and online" 
steam_checkpoint <- steam

Pretty happy with steam data - Could adjust average/median playtime to be hours (assuming that they are currently recorded in minutes), or adding a ratio

to ratings (similar to how steam does it) __________

CHECKPOINT Don’t bother running anything above this - anything worth saving has been saved in

write_csv(steam_checkpoint, "raw_data/steam_checkpoint.csv")

LS0tDQp0aXRsZTogIkxvb2tpbmcgYXQgZGF0YSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCkknbSBsb29raW5nIGF0IGdhbWVzIHNhbGVzIGRhdGEgLSBUaGlzIGlzIGhvcGVmdWxseSBnb2luZyB0byBsb29rIGxpa2UgYSBjb21iaW5hdGlvbiBvZiB0b3RhbCBzYWxlcyBzdGF0cywgdHJlbmRzIGJ5IGdlbnJlIChpZiB0aGVyZSBpcyB0aGF0IG11Y2ggZGV0YWlsKSwgcmV2aWV3cyg/KQ0KDQpIYXZlbid0IGZ1bGx5IGRlY2lkZWQgb24gdGhlIHF1ZXN0aW9uIEkgd2FudCB0byB0YWNrbGUsIGJ1dCBJIHRoaW5rIEknbSBhcHByb2FjaGluZyB0aGUgc2NlbmFyaW8gaW4gYSBzaW1pbGFyIHdheSB0byB0aGUgYnJpZWYNCg0KIlNtYWxsIGdhbWVzIGNvbXBhbnkgd2FudCB0byB1bmRlcnN0YW5kIHdoYXQgdHlwZXMgb2YgZ2FtZXMgc2VsbCBhIGxvdCBvZiBjb3BpZXMuIEluIHBhcnRpY3VsYXIgdGhleSBhcmUgbG9va2luZyBmb3IgYW5hbHlzaXMgdGhhdCBoZWxwcyB0aGVtIGRlY2lkZSB3aGljaCBkaXJlY3Rpb24gdG8gdGFrZSB0aGVpciBjb21wYW55IGluLiINCg0KSSdtIG5vdCB2ZXJ5IGhhcHB5IHdpdGggdGhlIGRhdGFzZXQsIGFzIHRoZSBsYW5kc2NhcGUgaW4gMjAxOS8xNiB3YXMgdmFzdGx5IGRpZmZlcmVudCB0byAyMDIzLiANCg0KDQojIyBJc3N1ZXMgaSdtIGV4cGVjdGluZyANCg0KLSBHYW1lcyBhcyBhIHNlcnZpY2UgYXMgYSBtb2RlbCBzdGlsbCBpc24ndCBkZWFkLCBzbyB0aGUgdHJhZGl0aW9uYWwgbWV0aG9kIG9mIGxvb2tpbmcgYXQgU2FsZXMgdG8ganVkZ2Ugc3VjY2VzcyBtaWdodCBub3QgYmUgZW5vdWdoDQoNCi0gU3Vic2NyaXB0aW9uIHNlcnZpY2VzIC0gY2hpZWZseSBHYW1lIFBhc3MsIGJ1dCBub3dhZGF5cyB5b3UgZG9uJ3QgcmVhbGx5IG5lZWQgdG8gYnV5IGEgZ2FtZSB5b3UncmUgaW50ZXJlc3RlZCBpbiAtIFNpbWlsYXIgbW9kZWwgdG8gbmV0ZmxpeA0KDQotIERMQyBhbmQgcG9zdC1sYXVuY2ggY29udGVudCAtIFlvdSBhcmVuJ3QgZmluaXNoZWQgYnV5aW5nIGEgZ2FtZSB3aGVuIHlvdSBwYXkgZm9yIGl0IGF0IGxhdW5jaC4gUGFpZCBjb250ZW50IGlzIGRyaXAgZmVkIGZvciB1cCB0byBzZXZlcmFsIHllYXJzIGFmdGVyIGEgZ2FtZXMgbGF1bmNoDQoNCi0gTWlzc2luZyBzYWxlcyBudW1iZXJzIC0gQXMgZmFyIGFzIEknbSBhd2FyZSwgYSBkZXZlbG9wZXIvcHVibGlzaGVyIGRvZXNuJ3QgaGF2ZSB0byByZWxlYXNlIHNhbGVzIG51bWJlcnMuDQoNCi0gTWlzc2luZyB0aXRsZXMgLSBJdCBtaWdodCBiZSBoYXJkIHRvIGJlbGlldmUsIGJ1dCB0aGVyZSBhcmUgbWFueSBtb3JlIGdhbWVzIG91dCB0aGVyZSB0aGFuIEZJRkEsIFNreXJpbSBhbmQgTWFyaW8gS2FydC4gU21hbGxlciBvciBtb3JlIG5pY2hlIHRpdGxlcyBtYXkgbm90IGJlIHJlcHJlc2VudGVkIF9fZXZlbiBpZiB0aGV5IGFyZSBhIGJldHRlciBtb2RlbCBmb3IgYSBzbWFsbGVyIGNvbXBhbnkgdG8gZW11bGF0ZV9fDQoNCi0gX19TdGVhbSBhbG9uZSBjb250YWlucyBvdmVyIDUwIHRob3VzYW5kIGdhbWVzX18gKGdyYW50ZWQsIG9mIGV4dHJlbWVseSB2YXJ5aW5nIHF1YWxpdHkpDQoNCi0gTWVhc3VyZXMgb2Ygc3VjY2VzcyAtIEl0IHNpbXBseSBpc24ndCBmYWlyIHRvIGp1ZGdlIHN1Y2Nlc3MgYmV0d2VlbiBzb21lIHRpdGxlcy4gQ29tcGFyaW5nIExldGhhbCBMZWFndWUgQmxhemUgdG8gU3VwZXIgU21hc2ggQnJvdGhlcnMgVWx0aW1hdGUgaXMgbm90DQphIGxldmVsIHBsYXlpbmcgZmllbGQuDQoNCiMjIEFzc3VtcHRpb25zDQoNCkJlZm9yZSBJIGV2ZW4gbG9vayBhdCB0aGUgZGF0YSwgSSdtIGV4cGVjdGluZyBzb21lIGJpYXMgLSBOZXdlciBjb25zb2xlcyBtaWdodCBub3QgYmUgcmVwcmVzZW50ZWQgaW4gZGF0YSwgd2hpY2ggbWlnaHQgInN1Z2dlc3QiIHRoYXQgaXQncyBiZXR0ZXIgdG8gZGV2ZWxvcCBhIHRpdGxlIGZvciBhbiBvbGRlciBzeXN0ZW0sIGFzIHRoZXkgaGF2ZSAibW9yZSBzdWNjZXNzIi4NCg0KU29tZSB0aXRsZXMgd2lsbCBibG93IG90aGVycyBvdXQgdGhlIHdhdGVyIGluIHRlcm1zIG9mIHNhbGVzLCBzaW1wbHkgZHVlIHRvIHRoZW0gYmVpbmcgbXVsdGktcGxhdGZvcm0uDQpGb3IgZXhhbXBsZSwgYSBzdWNjZXNzZnVsIFBTNCBleGNsdXNpdmUgdGl0bGUgd2lsbCBsaWtlbHkgbm90IHNlbGwgYXMgbWFueSBjb3BpZXMgYXMgYSBzdWNjZXNzZnVsIG11bHRpLXBsYXRmb3JtIHRpdGxlDQoNCk9uZSBvZiB0aGUgYmlnZ2VzdCBpbmZsdWVuY2VzIG9uIHNhbGVzIHdpbGwgYmUgVGl0bGUvRnJhbmNoaXNlIC0gSWYgYSBnYW1lIGlzIGNhbGxlZCAiUG9rZW1vbiIsIGNoYW5jZXMgYXJlIGl0J2xsIGRvIHdlbGwuIFRoaXMgaXMgbm90IHNvbWV0aGluZyB0aGF0IGEgc21hbGwgY29tcGFueSB3aWxsIGJlIGFibGUgdG8NCmVtdWxhdGUsIHVubGVzcyB0aGV5IGFyZSBsb29raW5nIHRvIGdldCBzdWVkLg0KDQojIF9fVkFNUElSRSBTVVJWSVZPUlMgREVGSUVTIEFMTCBMT0dJQyBBTkQgUFJFRElDVElPTlMgLSBQRVJIQVBTIE1BTktJTkQgV0FTTlQgTUVBTlQgVE8gS05PVyBXSEFUIEdBTUVTIFdJTEwgVEFLRSBPRkZfXw0KDQpDdXJyZW50bHkgaSBoYXZlIGEgZmV3IGRhdGFzZXRzIHRoYXQgbWlnaHQgYmUgdXNlZnVsIGZvciBzdXBwbGVtZW50aW5nIHRoZSAyMDE5IGRhdGEgZnJvbSBvbmUgb2YgdGhlIGV4YW1wbGUgYnJpZWZzDQoNCkh5cG90aGVzaXMgdGVzdCAtIEZyYW5jaGlzZSB2IG5vbi1GcmFuY2hpc2UNCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkodGlkeXRleHQpDQpsaWJyYXJ5KGdnd29yZGNsb3VkKQ0KbGlicmFyeShqYW5pdG9yKQ0KDQpzdGVhbSA8LSByZWFkX2NzdigicmF3X2RhdGEvc3RlYW0uY3N2IikgIyBhYnNvbHV0ZWx5IG1hc3NpdmUNCmdhbWVzXzEgPC0gcmVhZF9jc3YoInJhd19kYXRhL1ZpZGVvX0dhbWVzLmNzdiIpDQpnYW1lc18yIDwtIHJlYWRfY3N2KCJyYXdfZGF0YS9WaWRlb19HYW1lc19TYWxlcy5jc3YiKQ0KZ2FtZXNfcmF3IDwtIHJlYWRfY3N2KCJyYXdfZGF0YS9SYXcgRGF0YSBHVkdTJlIuY3N2IikNCmdhbWVzX2NsZWFuZWQgPC0gcmVhZF9jc3YoInJhd19kYXRhL0NsZWFuZWQgRGF0YSAyIEdWR1MmUi5jc3YiKQ0KYGBgDQpgYGB7cn0NCmdhbWVzX3JhdyAlPiUgDQogIGFycmFuZ2UoTmFtZSkgJT4lIA0KICBkcm9wX25hKEdsb2JhbF9TYWxlcykgIyBObyBOQSdzIGluIGdsb2JhbCBzYWxlcyAtIGdvb2Qgc3RhcnQNCmBgYA0KDQpgYGB7cn0NCg0KDQpgYGANCg0KYGBge3J9DQpzdGVhbV90YWdzIDwtIHN0ZWFtICU+JSANCiAgc2VsZWN0KHN0ZWFtc3B5X3RhZ3MpICU+JSANCiAgc2VwYXJhdGUoY29sID0gc3RlYW1zcHlfdGFncywgc2VwID0gIjsiLCBpbnRvID0gYygidGFncyIsICJ0YWdzXzIiLCAidGFnc18zIikpICU+JSANCiAgcGl2b3RfbG9uZ2VyKGNvbCA9IHN0YXJ0c193aXRoKCJ0YWdzIiksIHZhbHVlc190byA9ICJhbGxfdGFncyIpICU+JSANCiAgc2VsZWN0KGFsbF90YWdzKSAlPiUgDQogIGRyb3BfbmEoKSAlPiUgDQogIGNvdW50KGFsbF90YWdzKQ0KDQoNCmdnd29yZGNsb3VkKHdvcmRzID0gc3RlYW1fdGFncyRhbGxfdGFncywgZnJlcSA9IHN0ZWFtX3RhZ3MkbiwgcmFuZG9tLmNvbG9yID0gVFJVRSwgY29sb3JzID0gIGMoIiNmZjU5NWUiLCAiI2ZmY2EzYSIsICIjOGFjOTI2IiwgIiMxOTgyYzQiLCAiIzZhNGM5MyIpKQ0KDQpgYGANCg0KYGBge3J9DQoNCmBgYA0KDQoNCmBgYHtyfQ0Kc3RlYW0gJT4lIA0KICBhcnJhbmdlKGRlc2MobmFtZSkpDQpgYGANCg0KYGBge3J9DQpnYW1lc19iYWNrbG9nZ2QgPC0gcmVhZF9jc3YoInJhd19kYXRhL2dhbWVzIGJhY2tsb2dnZC5jc3YiKQ0KYGBgDQoNCiMjIENsZWFuaW5nIEJhY2tsb2dnZCBkYXRhDQoNCmBgYHtyfQ0KZ2FtZXNfYmFja2xvZ2dkIDwtIGdhbWVzX2JhY2tsb2dnZCAlPiUgDQogIGNsZWFuX25hbWVzKCkNCg0KIyBkcm9wcGluZyB1bnJlbGVhc2VkIGdhbWVzIGZvciBub3cgLSBvbmx5IDMgb2YgdGhlbSwgc28gd29uJ3QgaW1wYWN0IGFueXRoaW5nIHRvbyBtdWNoDQojIGNvbnZlcnRpbmcgcmVsZWFzZV9kYXRlIGludG8gYSBkYXRlIGFuZCBhZGRpbmcgYSB5ZWFycyBzaW5jZSByZWxlYXNlIGNvbHVtbiAtIFRoaXMgY291bGQgYmUgdXNhYmxlIHRvIGRpc2NvdW50IGNlcnRhaW4gdGl0bGVzIGZyb20gYSBtb2RlbA0KIyBlLmcgaWYgdGhlcmUgV0FTIGEgdHJlbmQgaW4gZ2FtZXMgMTB+IHllYXJzIGFnbywgaXQgbWF5IG5vdCBiZSBhcHBsaWNhYmxlIHRvZGF5LCBhbmQgbWlnaHQgcHJvZHVjZSBpbmNvcnJlY3QgZ3VpZGFuY2UNCiMgc3BsaXR0aW5nIGdlbnJlcyBpbnRvIHNlcGFyYXRlIGNvbHVtbnMgOiBvbmx5IG9uZSBnYW1lIGhhcyA3IGdlbnJlIHRhZ3MuIA0KZ2FtZXNfYmFja2xvZ2dkIDwtIGdhbWVzX2JhY2tsb2dnZCAlPiUgDQogIGFycmFuZ2UoZGVzYyhyYXRpbmcpKSAlPiUgDQogIGZpbHRlcihyZWxlYXNlX2RhdGUgIT0gInJlbGVhc2VzIG9uIFRCRCIpICU+JSANCiAgbXV0YXRlKHJlbGVhc2VfZGF0ZSA9IG1keShyZWxlYXNlX2RhdGUpLA0KICAgICAgICAgdGltZV9zaW5jZV9yZWxlYXNlID0gYXMucGVyaW9kKHRvZGF5KCkgLSByZWxlYXNlX2RhdGUpLA0KICAgICAgICAgeWVhcnNfc2luY2VfcmVsZWFzZSA9IGFzLm51bWVyaWModGltZV9zaW5jZV9yZWxlYXNlLCAieWVhcnMiKSwgLmFmdGVyID0gcmVsZWFzZV9kYXRlKSAlPiUgDQogIG11dGF0ZSh5ZWFyc19zaW5jZV9yZWxlYXNlID0gcm91bmQoeWVhcnNfc2luY2VfcmVsZWFzZSwgZGlnaXRzID0gMikpICU+JSANCiAgc2VsZWN0KCF0aW1lX3NpbmNlX3JlbGVhc2UpICU+JSANCiAgc2VwYXJhdGUoY29sID0gZ2VucmVzLCBzZXAgPSAiJywgJyIsIGludG8gPSBjKCJnZW5yZV90YWciLCAiZ2VucmVfdGFnXzIiLCAiZ2VucmVfdGFnXzMiLCAiZ2VucmVfdGFnXzQiLCAiZ2VucmVfdGFnXzUiLCAiZ2VucmVfdGFnXzYiLCAiZ2VucmVfdGFnXzciKSkNCg0KIyByZW1vdmluZyB0aGUgb3BlbmluZyBhbmQgY2xvc2luZyBicmFja2V0cyBhbmQgYXBvc3Ryb3BoZXMgYWNyb3NzIHRoZSBnZW5yZSBjb2x1bW5zDQpnYW1lc19iYWNrbG9nZ2QgPC0gZ2FtZXNfYmFja2xvZ2dkICU+JSANCiAgbXV0YXRlKGdlbnJlX3RhZyA9IHN0cl9yZXBsYWNlX2FsbChnZW5yZV90YWcsIHBhdHRlcm4gPSAiXFxbJyIsIHJlcGxhY2VtZW50ID0gIiIpLA0KICAgICAgICAgYWNyb3NzKHN0YXJ0c193aXRoKCJnZW5yZV90YWciKSwgfiBzdHJfcmVwbGFjZV9hbGwoLiwgcGF0dGVybiA9ICInXFxdIiwgcmVwbGFjZW1lbnQgPSAiIikpKSANCg0KZ2FtZXNfYmFja2xvZ2dkDQoNCmBgYA0KYGBge3J9DQojIENvbnZlcnRlZCB0aGUgYmVsb3cgY29kZSBpbnRvIGEgZnVuY3Rpb24gc28gaSBjYW4gcmV1c2UgaXQNCmNoYXJhY3Rlcl9rX3RvX251bWVyaWMgPC0gZnVuY3Rpb24oeCkgew0KICB4ID0gZW5xdW8oeCkNCiAgDQogIGdhbWVzX2JhY2tsb2dnZCAlPiUgDQogICAgIG11dGF0ZShtdWx0aXBsaWVyID0gY2FzZV93aGVuKA0KICAgIHN0cl9kZXRlY3QoISF4LCBwYXR0ZXJuID0gIkskIikgfiBUUlVFLA0KICAgIFRSVUUgfiBGQUxTRSkpICU+JSANCiAgbXV0YXRlKCEheCA6PSBzdHJfcmVtb3ZlKHN0cmluZyA9ICEheCwgcGF0dGVybiA9ICJLJCIpKSAlPiUgDQogIG11dGF0ZSghIXggOj0gYXMubnVtZXJpYyghIXgpKSAlPiUgDQogIG11dGF0ZSghIXggOj0gY2FzZV93aGVuKA0KICAgIG11bHRpcGxpZXIgPT0gVFJVRSB+ICEheCoxMDAwLA0KICAgIFRSVUUgfiAhIXgNCiAgKSkgJT4lIA0KICBzZWxlY3QoIW11bHRpcGxpZXIpDQp9DQoNCiMgY29udmVydHMgdGhlc2UgY29sdW1ucyBpbnRvIG51bWVyaWNzDQoNCmdhbWVzX2JhY2tsb2dnZCA8LSBjaGFyYWN0ZXJfa190b19udW1lcmljKHggPSBudW1iZXJfb2ZfcmV2aWV3cykNCg0KZ2FtZXNfYmFja2xvZ2dkIDwtIGNoYXJhY3Rlcl9rX3RvX251bWVyaWMoeCA9IHBsYXlzKQ0KDQpnYW1lc19iYWNrbG9nZ2QgPC0gY2hhcmFjdGVyX2tfdG9fbnVtZXJpYyh4ID0gd2lzaGxpc3QpDQpgYGANCg0KYGBge3J9DQpnYW1lc19iYWNrbG9nZ2QgPC0gZ2FtZXNfYmFja2xvZ2dkICU+JSANCiAgc2VsZWN0KCFwbGF5cywgIWJhY2tsb2dzLCAhdGltZXNfbGlzdGVkKQ0KYGBgDQoNCmNodW5rIGJlbG93IGlzIG9yaWdpbmFsIGF0dGVtcHRzIGF0IGNvbnZlcnRpbmcgdGhlIGFib3ZlIGNvbHVtbnMNCmBgYHtyfQ0KDQojIGdhbWVzX2JhY2tsb2dnZCA8LSBnYW1lc19iYWNrbG9nZ2QgJT4lIA0KIyAgIHNlbGVjdCghdGltZXNfbGlzdGVkKQ0KDQojIG11dGF0aW5nIG51bWJlciBvZiByZXZpZXdzIHRvIGJlIG51bWVyaWMNCiMgSW4gdGhpcyBob3JyaWJsZSBzdGVwLCBJJ20gdXNpbmcgc3RyX2RldGVjdCBhbmQgY2FzZV93aGVuIHRvIGNyZWF0ZSBhIGNvbHVtbiBjYWxsZWQgbXVsdGlwbGllciAtIHRoaXMgd2lsbCBiZSB1c2VkIGxhdGVyDQojIHRoZW4gaSBkcm9wIHRoZSAiSyIncywgYW5kIGNvbnZlcnQgdGhlIGNvbHVtbiB0byBhIG51bWVyaWMNCiMgdGhlbiBtdWx0aXBseSB0aGUgY29sdW1ucyB2YWx1ZXMgYnkgMTAwMCBJRiB0aGUgbXVsdGlwbGllciBjb2x1bW4gaXMgVFJVRQ0KIyBnYW1lc19iYWNrbG9nZ2QgJT4lDQojICAgbXV0YXRlKG11bHRpcGxpZXIgPSBjYXNlX3doZW4oDQojICAgICBzdHJfZGV0ZWN0KG51bWJlcl9vZl9yZXZpZXdzLCBwYXR0ZXJuID0gIkskIikgfiBUUlVFLA0KIyAgICAgVFJVRSB+IEZBTFNFDQojICAgKSwuYWZ0ZXIgPSBudW1iZXJfb2ZfcmV2aWV3cykgJT4lDQojICAgbXV0YXRlKG51bWJlcl9vZl9yZXZpZXdzID0gc3RyX3JlbW92ZShudW1iZXJfb2ZfcmV2aWV3cywgcGF0dGVybiA9ICJLJCIpKSAlPiUNCiMgICBtdXRhdGUobnVtYmVyX29mX3Jldmlld3MgPSBhcy5udW1lcmljKG51bWJlcl9vZl9yZXZpZXdzKSkgJT4lDQojICAgbXV0YXRlKG51bWJlcl9vZl9yZXZpZXdzID0gY2FzZV93aGVuKA0KIyAgICAgbXVsdGlwbGllciA9PSBUUlVFIH4gbnVtYmVyX29mX3Jldmlld3MqMTAwMCwNCiMgICAgIFRSVUUgfiBudW1iZXJfb2ZfcmV2aWV3cw0KIyAgICkpICU+JQ0KIyAgIHNlbGVjdCghbXVsdGlwbGllcikNCiMgDQojICMgT2ggTm8gaSBuZWVkIHRvIGRvIHRoaXMgdG8gbW9yZSBjb2x1bW5zDQojIA0KIyBnYW1lc19iYWNrbG9nZ2QgJT4lIA0KIyAgICBtdXRhdGUobXVsdGlwbGllciA9IGNhc2Vfd2hlbigNCiMgICAgIHN0cl9kZXRlY3QocGxheXMsIHBhdHRlcm4gPSAiSyQiKSB+IFRSVUUsDQojICAgICBUUlVFIH4gRkFMU0UNCiMgICApLC5hZnRlciA9IHBsYXlzKSAlPiUgDQojICAgbXV0YXRlKHBsYXlzID0gc3RyX3JlbW92ZShwbGF5cywgcGF0dGVybiA9ICJLJCIpKSAlPiUgDQojICAgbXV0YXRlKHBsYXlzID0gYXMubnVtZXJpYyhwbGF5cykpICU+JSANCiMgICBtdXRhdGUocGxheXMgPSBjYXNlX3doZW4oDQojICAgICBtdWx0aXBsaWVyID09IFRSVUUgfiBwbGF5cyoxMDAwLA0KIyAgICAgVFJVRSB+IHBsYXlzDQojICAgKSkgJT4lIA0KIyAgIHNlbGVjdCghbXVsdGlwbGllcikNCg0KIyBtYWtpbmcgaXQgYSBmdW5jdGlvbiBiZWNhdXNlIHRoYXQncyBlYXNpZXINCiMgY2hhcmFjdGVyX2tfdG9fbnVtZXJpYyA8LSBmdW5jdGlvbihkYXRhZnJhbWUsIGNvbHVtbikgew0KIyAgICAgIG11dGF0ZShtdWx0aXBsaWVyID0gY2FzZV93aGVuKA0KIyAgICAgc3RyX2RldGVjdChjb2x1bW4sIHBhdHRlcm4gPSAiSyQiKSB+IFRSVUUsDQojICAgICBUUlVFIH4gRkFMU0UNCiMgICApLC5hZnRlciA9IGNvbHVtbikgJT4lIA0KIyAgIG11dGF0ZShjb2x1bW4gPSBzdHJfcmVtb3ZlKGNvbHVtbiwgcGF0dGVybiA9ICJLJCIpKSAlPiUgDQojICAgbXV0YXRlKGNvbHVtbiA9IGFzLm51bWVyaWMoY29sdW1uKSkgJT4lIA0KIyAgIG11dGF0ZShjb2x1bW4gPSBjYXNlX3doZW4oDQojICAgICBtdWx0aXBsaWVyID09IFRSVUUgfiBjb2x1bW4qMTAwMCwNCiMgICAgIFRSVUUgfiBjb2x1bW4NCiMgICApKSAlPiUgDQojICAgc2VsZWN0KCFtdWx0aXBsaWVyKQ0KIyB9DQoNCmBgYA0KDQoNCmBgYHtyfQ0KIyB0aWR5aW5nIHVwIHRlYW0gY29sdW1uDQpnYW1lc19iYWNrbG9nZ2QgPC0gZ2FtZXNfYmFja2xvZ2dkICU+JSANCiAgbXV0YXRlKHRlYW0gPSBzdHJfcmVwbGFjZV9hbGwodGVhbSwgcGF0dGVybiA9ICJcXFsnIiwgcmVwbGFjZW1lbnQgPSAiIiksDQogICAgICAgICB0ZWFtID0gc3RyX3JlcGxhY2VfYWxsKHRlYW0sIHBhdHRlcm4gPSAiJ1xcXSIsIHJlcGxhY2VtZW50ID0gIiIpLA0KICAgICAgICAgdGVhbSA9IHN0cl9yZXBsYWNlX2FsbCh0ZWFtLCBwYXR0ZXJuID0gIlxcJywgXFwnIiwgcmVwbGFjZW1lbnQgPSAiLCAiKSkgDQpgYGANCg0KYGBge3J9DQojIGtlZXBpbmcgdGhlc2Ugc2VwYXJhdGUgaW5jYXNlIGkgbmVlZCB0aGVtDQpyZXZpZXdzX2JhY2tsb2dnZCA8LSBnYW1lc19iYWNrbG9nZ2QgJT4lIA0KICBzZWxlY3QodGl0bGUsIHN1bW1hcnksIHJldmlld3MpIA0KYGBgDQoNCmBgYHtyfQ0KIyBjdXR0aW5nIHRoZXNlIGNvbHVtbnMgZm9yIG5vdyAtIGlmIG5lZWRlZCwgaSdsbCByZWF0dGFjaCB0aGVtDQpnYW1lc19iYWNrbG9nZ2QgPC0gZ2FtZXNfYmFja2xvZ2dkICU+JSANCiAgc2VsZWN0KCF4MSkNCmBgYA0KDQpgYGB7cn0NCmdhbWVzX2JhY2tsb2dnZCA8LSBnYW1lc19iYWNrbG9nZ2QgJT4lIA0KICBzZWxlY3QoLXN1bW1hcnksIC10aW1lc19saXN0ZWQsIC1yZXZpZXdzLCAtcGxheWluZywgLWJhY2tsb2dzKSANCmBgYA0KDQpgYGB7cn0NCiMgcmVtb3ZpbmcgZHVwbGljYXRlZCByb3dzDQoNCmdhbWVzX2JhY2tsb2dnZCA8LSBnYW1lc19iYWNrbG9nZ2QgJT4lIA0KICB1bmlxdWUoKQ0KYGBgDQoNCiMjIEhhcHB5IHdpdGggZ2FtZXNfYmFja2xvZ2dlZCBqdXN0IG5vdw0KDQpgYGB7cn0NCmdhbWVzX2JhY2tsb2dnZF9jbGVhbiA8LSBnYW1lc19iYWNrbG9nZ2QNCndyaXRlX2NzdihnYW1lc19iYWNrbG9nZ2RfY2xlYW4sICJjbGVhbl9kYXRhL2JhY2tsb2dnZF9jbGVhbi5jc3YiKQ0KYGBgDQoNCiMjIExvb2tpbmcgYXQgdG9wIGdhbWVzIGRhdGENCg0KYGBge3J9DQpnYW1lc18xIA0KYGBgDQoNCmBgYHtyfQ0KZ2FtZXNfMg0KYGBgDQpUaGVzZSBzZWVtIHRvIGJlIHRoZSBzYW1lIHRoaW5nDQoNCmdvaW5nIHdpdGggZ2FtZXNfMSBhbmQgcmVuYW1pbmcgaXQgc29tZXRoaW5nIG1vcmUgc2Vuc2libGUNCg0KYGBge3J9DQpnYW1lc19zYWxlcyA8LSBnYW1lc18xDQpgYGANCg0KYGBge3J9DQpnYW1lc19zYWxlcyA8LSBnYW1lc19zYWxlcyAlPiUgDQogIGNsZWFuX25hbWVzKCkgDQpgYGANCg0KYGBge3J9DQpnYW1lc19zYWxlcyAlPiUgDQogIGZpbHRlcihuYW1lID09ICJNaW5lY3JhZnQiKSAlPiUgDQogIGNvdW50KHN1bShnbG9iYWxfc2FsZXMpKQ0KYGBgDQoNCmBgYHtyfQ0KIyBnYW1lc19zYWxlcyAlPiUgDQojICAgZ3JvdXBfYnkobmFtZSkgJT4lIA0KICANCmBgYA0KIyBtaWdodCBoYXZlIHRvIGZhZmYgYXJvdW5kIHRvIGdldCBhbiBhY2N1cmF0ZSBwaWN0dXJlIG9mIHNhbGVzIGZvciB0aGlzIG9uZQ0KDQpgYGB7cn0NCnZnX2NoYXJ0el9mZWIyMyA8LSByZWFkX2NzdigicmF3X2RhdGEvZ2FtZV9zdGF0aXN0aWNzX2ZlYl8yMDIzLmNzdiIpDQpgYGANCg0KYGBge3J9DQp2Z19jaGFydHpfZmViMjMNCmBgYA0KYGBge3J9DQojIGRvbnQgcmVhbGx5IG5lZWQgdG8ga25vdyB3aGVyZSB0aGVzZSBnYW1lcyBmYWxsIG9uIHRoZSBjaGFydHMgZm9yIHRoaXMgc2l0ZSAtIG5vdGU6IHBvcyBtZWFucyBwb3NpdGlvbi4gTm90IGFueXRoaW5nIGVsc2UuDQp2Z19jaGFydHpfZmViMjMgPC0gdmdfY2hhcnR6X2ZlYjIzICU+JSANCiAgc2VsZWN0KC1wb3MpDQpgYGANCg0KUGxhdGZvcm0gaXMgYSBsaXR0bGUgdW5oZWxwZnVsLiAiQWxsIiBkb2VzIG5vdCBtZWFuIGFsbCBwbGF0Zm9ybXMgKHVubGVzcyBFbGRlbiBSaW5nIHJlbGVhc2VkIG9uIFN3aXRjaCwgb3IgdGhlIE5FUykNCiJTZXJpZXMiIG1pZ2h0IGJlIHVzZWZ1bCBhcyBhIGxvb2stdXAgZm9yIGFub3RoZXIgdGFibGUgdGhvdWdoDQoNCmBgYHtyfQ0KbGlzdF9vZl9mcmFuY2hpc2VzIDwtIHZnX2NoYXJ0el9mZWIyMyAlPiUgDQogIGZpbHRlcihwbGF0Zm9ybSA9PSAiU2VyaWVzIikgJT4lIA0KICBzZWxlY3QodGl0bGUsIHB1Ymxpc2hlciwgZGV2ZWxvcGVyKQ0KYGBgDQoNCmBgYHtyfQ0Kd3JpdGVfY3N2KGxpc3Rfb2ZfZnJhbmNoaXNlcywgImNsZWFuX2RhdGEvbGlzdF9vZl9mcmFuY2hpc2VzLmNzdiIpDQpgYGANCg0KIyBUaGF0cyBhbGwgaSB3YW50IGZyb20gdGhpcyBvbmUgaSB0aGluay4gTWlnaHQgY29tZSBiYWNrIHRvIGl0DQoNCiMgQmFjayB0byBTdGVhbQ0KDQpgYGB7cn0NCnN0ZWFtDQpgYGANCg0KYGBge3J9DQojIGkgZG9uJ3Qga25vdyBtdWNoIGFib3V0IHRpdGxlcyB0aGF0IGFyZW4ndCBpbiBlbmdsaXNoIC0gYXJlIGFsbCB0aGUgb25lcyBpbiB0aGlzIGRhdGFzZXQgZW5nbGlzaD8NCg0Kc3RlYW0gPC0gc3RlYW0gJT4lIA0KICBmaWx0ZXIoZW5nbGlzaCA9PSAxKSAlPiUgDQogIHNlbGVjdCghZW5nbGlzaCkNCg0KIyBhcHByb3ggMTAwMCBhcmVuJ3QgLSBidXQgd2hvIGFyZSB0aGV5Pw0KDQojIHN0ZWFtICU+JSANCiMgICBmaWx0ZXIoZW5nbGlzaCA9PSAwKSAlPiUgDQojICAgZmlsdGVyKG93bmVycyAhPSAiMC0yMDAwMCIpDQoNCiMgTWFqb3JpdHkgc2VlbSB0byBiZSBjaGluZXNlL2phcGFuZXNlL3J1c3NpYW4gaW4gb3JpZ2luLiBQcm9iYWJseSBub3QgcmVhbGx5IGluIHRoZSBzY29wZSBvZiBhIGR1bmRlZSBiYXNlZCBjb21wYW55DQojIFRoaXMgbWFpbmx5IHN1Z2dlc3RzIHRoYXQgdGhlc2UgYXJlIHRpdGxlcyB0aGF0IGRvbid0IGhhdmUgc3VwcG9ydCBmb3IgIG11bHRpcGxlIGxhbmd1YWdlcw0KIyBBbHNvIGRvbid0IHNlZW0gdG8gaGF2ZSBhIGxvdCBvZiBzYWxlcy4gQ2FuIHByb2JhYmx5IGp1c3RpZnkganVzdCBkcm9wcGluZyB0aGVzZSBmb3IgdGhlIHRpbWUgYmVpbmcuDQpgYGANCg0KIyBIWVBPVEhFU0lTIFRFU1Q6IElOIFNURUFNIERBVEEsIERPRVMgSEFWSU5HIFNVUFBPUlQgRk9SIE1VTFRJUExFIExBTkdVQUdFUyBBRkZFQ1QgU0FMRVM/IA0KDQpgYGB7cn0NCiMgbWFrZSBwbGF0Zm9ybXMgd2lkZXIgLSB3aW5kb3dzX3N1cHBvcnQgPSBUUlVFL0ZBTFNFLCBtYWNfc3VwcG9ydCA9IFRSVUUvRkFMU0UsIGxpbnV4X3N1cHBvcnQgPSBUUlVFL0ZBTFNFDQpzdGVhbSA8LSBzdGVhbSAlPiUgDQogICBzZXBhcmF0ZShjb2wgPSBwbGF0Zm9ybXMsIHNlcCA9ICI7IiwgaW50byA9IGMoInBsYXRmb3JtXzEiLCAicGxhdGZvcm1fMiIsICJwbGF0Zm9ybV8zIikpIA0KYGBgDQpgYGB7cn0NCiMgdGhpcyBpcyB2ZXJib3NlIGFuZCBjbHVua3ksIGJ1dCBpdCBjYXRjaGVzIGV2ZXJ5dGhpbmcgc28gd2hhdGV2ZXINCnN0ZWFtIDwtIHN0ZWFtICU+JSANCiAgbXV0YXRlKHdpbmRvd3Nfc3VwcG9ydCA9IGNhc2Vfd2hlbigNCiAgICBwbGF0Zm9ybV8xID09ICJ3aW5kb3dzIiB+IFRSVUUsDQogICAgcGxhdGZvcm1fMiA9PSAid2luZG93cyIgfiBUUlVFLA0KICAgIHBsYXRmb3JtXzMgPT0gIndpbmRvd3MiIH4gVFJVRSwNCiAgICBUUlVFIH4gRkFMU0UgKSwuYWZ0ZXIgPSBwdWJsaXNoZXIpICU+JSANCiAgbXV0YXRlKG1hY19zdXBwb3J0ID0gY2FzZV93aGVuKA0KICAgIHBsYXRmb3JtXzEgPT0gIm1hYyIgfiBUUlVFLA0KICAgIHBsYXRmb3JtXzIgPT0gIm1hYyIgfiBUUlVFLA0KICAgIHBsYXRmb3JtXzMgPT0gIm1hYyIgfiBUUlVFLA0KICAgIFRSVUUgfiBGQUxTRSksIC5hZnRlciA9IHdpbmRvd3Nfc3VwcG9ydCkgJT4lIA0KIG11dGF0ZShsaW51eF9zdXBwb3J0ID0gY2FzZV93aGVuKA0KICAgIHBsYXRmb3JtXzEgPT0gImxpbnV4IiB+IFRSVUUsDQogICAgcGxhdGZvcm1fMiA9PSAibGludXgiIH4gVFJVRSwNCiAgICBwbGF0Zm9ybV8zID09ICJsaW51eCIgfiBUUlVFLA0KICAgIFRSVUUgfiBGQUxTRSksIC5hZnRlciA9IG1hY19zdXBwb3J0KSAlPiUgDQogIHNlbGVjdCgtcGxhdGZvcm1fMSwgLXBsYXRmb3JtXzIsIC1wbGF0Zm9ybV8zKQ0KYGBgDQoNCmBgYHtyfQ0KIyBjaGFuZ2Ugb3duZXJzIHRvIGJlIG1vcmUgcmVhZGFibGUNCnN0ZWFtIDwtIHN0ZWFtICU+JSANCiAgbXV0YXRlKG93bmVycyA9IGNhc2Vfd2hlbigNCiAgICBvd25lcnMgPT0gIjAtMjAwMDAiIH4gImJlbG93IDIwayIsDQogICAgb3duZXJzID09ICIyMDAwMC01MDAwMCIgfiAiMjBrIHRvIDUwayIsDQogICAgb3duZXJzID09ICI1MDAwMC0xMDAwMDAiIH4gIjUwayB0byAxMDBrIiwNCiAgICBvd25lcnMgPT0gIjEwMDAwMC0yMDAwMDAiIH4gIjEwMGsgdG8gMjAwayIsDQogICAgb3duZXJzID09ICIyMDAwMDAtNTAwMDAwIiB+ICIyMDBrIHRvIDUwMGsiLA0KICAgIG93bmVycyA9PSAiNTAwMDAwLTEwMDAwMDAiIH4gIjUwMGsgdG8gMU0iLA0KICAgIG93bmVycyA9PSAiMTAwMDAwMC0yMDAwMDAwIiB+ICIxTSB0byAyTSIsDQogICAgb3duZXJzID09ICIyMDAwMDAwLTUwMDAwMDAiIH4gIjJNIHRvIDVNIiwNCiAgICBvd25lcnMgPT0gIjUwMDAwMDAtMTAwMDAwMDAiIH4gIjVNIHRvIDEwTSIsDQogICAgb3duZXJzID09ICIxMDAwMDAwMC0yMDAwMDAwMCIgfiAiMTBNIHRvIDIwTSIsDQogICAgb3duZXJzID09ICIyMDAwMDAwMC01MDAwMDAwMCIgfiAiMjBNIHRvIDUwTSIsDQogICAgb3duZXJzID09ICI1MDAwMDAwMC0xMDAwMDAwMDAiIH4gIjUwTSB0byAxMDBNIiwNCiAgICBvd25lcnMgPT0gIjEwMDAwMDAwMC0yMDAwMDAwMDAiIH4gIjEwME0gdG8gMjAwTSINCiAgKSkgDQoNCiAgDQoNCmBgYA0KDQpgYGB7cn0NCnN0ZWFtICU+JSANCiAgZmlsdGVyKG93bmVycyA9PSAiMTBNIHRvIDIwTSIpDQpgYGANCg0KIyMgSU4gVEhJUyBWRVJTSU9OIE9GIFRIRSBTVEVBTSBEQVRBU0VULCBhbGwgYnV0IG9mIHRoZSBnYW1lcyBsaXN0ZWQgYXMgaGF2aW5nIG1vcmUgdGhhbiAyMCBtaWxsaW9uIHBsYXllcnMgYXJlIGZyZWVtaXVtPyBmcmVlIHRvIHN0YXJ0PyBmcmVlIHRvIHBsYXk/IChwaWNrIHlvdXIgcG9pc29uKSBnYW1lcw0KDQpgYGB7cn0NCiMgQURESU5HIEEgQ09MVU1OIFRPIEZJTkQgVEhFIEZSRUUgVE8gUExBWSBTVFVGRg0KDQpzdGVhbSA8LSBzdGVhbSAlPiUgDQogIG11dGF0ZShmcmVlX3RvX3BsYXkgPSBjYXNlX3doZW4oDQogICAgc3RyX2RldGVjdChnZW5yZXMsICJGcmVlIHRvIFBsYXkiKSB+IFRSVUUsDQogICAgc3RyX2RldGVjdChzdGVhbXNweV90YWdzLCJGcmVlIHRvIFBsYXkiKSB+IFRSVUUsDQogICAjIHByaWNlID09IDAuMDAgfiBUUlVFLA0KICAgIFRSVUUgfiBGQUxTRQ0KICApLC5hZnRlciA9IHB1Ymxpc2hlcikgDQpgYGANCg0KYGBge3J9DQpzdGVhbSAlPiUgDQogIGZpbHRlcihmcmVlX3RvX3BsYXkgPT0gVFJVRSkNCmBgYA0KDQoNCg0KYGBge3J9DQojIENvbHVtbiB0byBmaW5kIGFsbCB0aGUgVlIgc3R1ZmYNCnN0ZWFtIDwtIHN0ZWFtICU+JSANCiAgbXV0YXRlKHZpcnR1YWxfcmVhbGl0eV9zdXBwb3J0ID0gY2FzZV93aGVuKA0KICAgIHN0cl9kZXRlY3QobmFtZSwgIlZSIikgfiBUUlVFLA0KICAgIHN0cl9kZXRlY3Qoc3RlYW1zcHlfdGFncywiVlIiKSB+IFRSVUUsDQogICAgVFJVRSB+IEZBTFNFDQogICksLmFmdGVyID0gZnJlZV90b19wbGF5KQ0KYGBgDQoNCmBgYHtyfQ0Kc3RlYW0gJT4lIA0KICBmaWx0ZXIodmlydHVhbF9yZWFsaXR5X3N1cHBvcnQgPT0gVFJVRSkNCmBgYA0KYGBge3J9DQojIE5vdyB0aGUgc2FtZSBidXQgZm9yIG11bHRpLXBsYXllciAtIExvY2FsIGFuZCBPbmxpbmUgDQpzdGVhbSA8LSBzdGVhbSAlPiUgDQogIG11dGF0ZShtdWx0aXBsYXllciA9IGNhc2Vfd2hlbigNCiAgICBzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJMb2NhbCBNdWx0aS1QbGF5ZXIiKSAmIHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIk9ubGluZSBNdWx0aS1QbGF5ZXIiKSAmIHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIkNvLW9wIikgfiAiTG9jYWwgKyBPbmxpbmUgKyBDby1vcCIsDQogICAgc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiTG9jYWwgTXVsdGktUGxheWVyIikgJiBzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJPbmxpbmUgTXVsdGktUGxheWVyIikgJiAhc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiQ28tb3AiKSB+ICJMb2NhbCArIE9ubGluZSIsDQogICAgc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiTG9jYWwgTXVsdGktUGxheWVyIikgJiBzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJDby1vcCIpICYgIXN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIk9ubGluZSBNdWx0aS1QbGF5ZXIiKSB+ICJMb2NhbCArIENvLW9wIiwNCiAgICBzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJPbmxpbmUgTXVsdGktUGxheWVyIikgJiBzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJDby1vcCIpICYgIXN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIkxvY2FsIE11bHRpLVBsYXllciIpIH4gIk9ubGluZSArIENvLW9wIiwNCiAgICBzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJMb2NhbCBNdWx0aS1QbGF5ZXIiKSAmICFzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJPbmxpbmUgTXVsdGktUGxheWVyIikgfiAiTG9jYWwgb25seSIsDQogICAgc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiT25saW5lIE11bHRpLVBsYXllciIpICYgIXN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIkxvY2FsIE11bHRpLVBsYXllciIpIH4gIk9ubGluZSBvbmx5IiwNCiAgICBzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJNdWx0aS1wbGF5ZXIiKSAmIHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIk1NTyIpIHwgc3RyX2RldGVjdChzdGVhbXNweV90YWdzLCAiTU1PIikgfiAiT25saW5lIG9ubHkiLA0KICAgIHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIkNvLW9wIikgfiAiQ28tb3AgbXVsdGlwbGF5ZXIiLA0KICAgIHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIk11bHRpLXBsYXllciIpICYgIXN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIk9ubGluZSBNdWx0aS1QbGF5ZXIiKSAmICFzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJMb2NhbCBNdWx0aS1QbGF5ZXIiKSB+ICJNdWx0aXBsYXllciAoVW5zcGVjaWZpZWQpIiwNCiAgICBUUlVFIH4gIk5vIG11bHRpcGxheWVyIg0KICApLC5hZnRlciA9IHZpcnR1YWxfcmVhbGl0eV9zdXBwb3J0KSANCg0KIyBJIHRoaW5rIGkgY291bGQgYWRkIG1vcmUgbGV2ZWxzLCBidXQgbm90IHJpZ2h0IG5vdw0KIyByZW1lbWJlciBleGFjdGx5IG9uZSBjb21tZW50IGFnbyB3aGVuIGkgc2FpZCAibG9jYWwgYW5kIG9ubGluZSIgDQpgYGANCg0KYGBge3J9DQpzdGVhbV9jaGVja3BvaW50IDwtIHN0ZWFtDQpgYGANCg0KIyBQcmV0dHkgaGFwcHkgd2l0aCBzdGVhbSBkYXRhIC0gQ291bGQgYWRqdXN0IGF2ZXJhZ2UvbWVkaWFuIHBsYXl0aW1lIHRvIGJlIGhvdXJzIChhc3N1bWluZyB0aGF0IHRoZXkgYXJlIGN1cnJlbnRseSByZWNvcmRlZCBpbiBtaW51dGVzKSwgb3IgYWRkaW5nIGEgcmF0aW8gDQogIHRvIHJhdGluZ3MgKHNpbWlsYXIgdG8gaG93IHN0ZWFtIGRvZXMgaXQpDQpfX19fX19fX19fDQogIA0KICBDSEVDS1BPSU5UDQpEb24ndCBib3RoZXIgcnVubmluZyBhbnl0aGluZyBhYm92ZSB0aGlzIC0gYW55dGhpbmcgd29ydGggc2F2aW5nIGhhcyBiZWVuIHNhdmVkIGluIA0KICANCmBgYHtyfQ0Kd3JpdGVfY3N2KHN0ZWFtX2NoZWNrcG9pbnQsICJyYXdfZGF0YS9zdGVhbV9jaGVja3BvaW50LmNzdiIpDQpgYGANCg0KX19fX19fX19fDQoNCg0KDQoNCg==